import {
  Controller,
  Get,
  Post,
  Body,
  HttpException,
  HttpStatus,
} from '@nestjs/common';
import * as _ from 'lodash';

import { InfluxDB } from 'influx';
import { EquipmentDataService, IEquipmentData } from './equipment-data.service';

export interface IQuery {
  equipmentId: string;
  equipmentModelId: string;
  startTime?: number;
  endTime?: number;
}

export interface IEquipmentDataResponse {
  time: number;
  [key: string]: any;
}

export interface IQueryResponse {
  results: IEquipmentDataResponse[];
  query: IQuery;
}

@Controller('equipment-data/query')
export class EquipmentDataController {
  constructor(private readonly service: EquipmentDataService) {}

  @Post()
  public async query(@Body() query: IQuery): Promise<IQueryResponse> {
    if (!query) {
      throw new HttpException('query object required', HttpStatus.BAD_REQUEST);
    }
    const qstr = this.buildQueryString(query);
    const results = await this.service.query(qstr);
    const res = results.map(datum => {
      const d: IEquipmentDataResponse = _.merge(
        { time: 0, equipmentModelId: query.equipmentModelId },
        _.omit(datum, ['time']),
      );
      if (
        typeof datum.time !== 'string' &&
        typeof datum.time.getNanoTime === 'function'
      ) {
        d.time = datum.time.getTime();
      } else {
        d.time = new Date(datum.time).getTime();
      }
      delete (d as any).host;
      const dn: IEquipmentDataResponse = {
        time: d.time,
        equipmentId: d.equipmentId,
        equipmentModelId: query.equipmentModelId,
        producer: d.producer,
        processTimestamp: d.processTimestamp,
      };
      Object.keys(d).forEach(key => {
        if (key.startsWith('last_')) {
          const nk = key.slice(5);
          dn[nk] = d[key];
        }
      });
      return dn;
    });
    const response = {
      results: res,
      query,
    };
    return response;
  }

  private buildQueryString(query: IQuery): string {
    if (!query.equipmentId || query.equipmentId === '') {
      throw new HttpException(
        'must provide valid equipment id',
        HttpStatus.BAD_REQUEST,
      );
    }
    if (!query.equipmentModelId || query.equipmentModelId === '') {
      throw new HttpException(
        'must provide equipment model id',
        HttpStatus.BAD_REQUEST,
      );
    }
    const span = this.buildTimespan(query);
    return `SELECT first(*) FROM "equipmenttest3"."autogen"."${
      query.equipmentModelId
    }" WHERE "equipmentId" = '${
      query.equipmentId
    }' AND ${span} GROUP BY time(60s)`;
  }

  private buildTimespan(query: IQuery): string {
    if (
      typeof query.startTime === 'number' &&
      typeof query.endTime === 'number'
    ) {
      // query with start and end
      if (query.startTime >= query.endTime) {
        throw new HttpException(
          'must provide valid time span',
          HttpStatus.BAD_REQUEST,
        );
      }
      const span = query.endTime - query.startTime;
      if (span > 10 * 60 * 1000) {
        throw new HttpException('time span too large', HttpStatus.BAD_REQUEST);
      }
      const start = query.startTime * 1000 * 1000;
      const end = query.endTime * 1000 * 1000;
      return `time > ${start} AND time < ${end}`;
    } else if (typeof query.startTime === 'number') {
      // query with start
      const end = Date.now();
      if (query.startTime > end) {
        throw new HttpException(
          'must provide valid time span',
          HttpStatus.BAD_REQUEST,
        );
      }
      const span = end - query.startTime;
      if (span > 10 * 60 * 1000) {
        throw new HttpException('time span too large', HttpStatus.BAD_REQUEST);
      }
      const start = query.startTime * 1000 * 1000;
      return `time > ${start}`;
    } else if (typeof query.endTime === 'number') {
      // query with end
      let start = query.endTime - 10 * 60 * 1000;
      if (start < 0) {
        start = 0;
      }
      return `time > ${start * 1000 * 1009} AND time < ${query.endTime *
        1000 *
        1000}`;
    } else {
      const start = Date.now() - 10 * 60 * 1000;
      return `time > ${start * 1000 * 1000}`;
    }
  }
}
